package org.opencv.samples.imagemanipulations;

import java.util.Arrays;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.imgproc.Imgproc;
import org.opencv.samples.imagemanipulations.R;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.Toast;

public class ImageManipulationsActivity extends Activity implements CvCameraViewListener2 {
    private static final String  TAG                 = "OCVSample::Activity";

    /**
     * 
     * Controls
     * 
     */
    // Menu Items for Filters
    private MenuItem			mItemNeg;
    private SubMenu				mItemSharpen;
    private SubMenu				mItemBlur;
    private MenuItem			mItemGauss_5;
    private MenuItem			mItemGauss_11;
    private MenuItem			mItemMedian_7;
    private MenuItem			mItemMedian_5;
    private MenuItem			mItemFFT;
    private MenuItem			mItemLapS;
    private MenuItem			mItemHist;
    
    // Checkbox
    private CheckBox chkHistNorm;
    
    /**
     * 
     * Global Variables
     */
    // Identify filters
    public static final int		MODE_NEG	= 0;
    public static final int		MODE_GAUSS_5= 1;
    public static final int		MODE_GAUSS_11= 7;
    public static final int		MODE_BWLOW= 11;
    public static final int		MODE_MEDIAN_7 = 2;
    public static final int		MODE_MEDIAN_5 = 6;
    public static final int		MODE_FFT	= 3;
    public static final int 	MODE_LAPS	= 4;
    public static final int 	MODE_HIST	= 5;
    public static final int 	MODE_HIGHPASS	= 8;
    public static final int		MODE_SOBEL_VER = 9;
    public static final int		MODE_SOBEL_HOR = 10;
    // Current Mode
    public static int			curMode		= MODE_HIST;
    
    // Switches for processing
    	// perform histogram normalization
    private boolean bHistNorm = false;
    
    // CameraView
    private CameraBridgeViewBase mOpenCvCameraView;

    private Size                 mSize0;
    private Size                 mSizeRgba;
    private Size                 mSizeRgbaInner;

    private Mat                  mRgba;
    private Mat                  mGray;
    private Mat                  mIntermediateMat;

    private Mat                  mRgbaInnerWindow;
    private Mat                  mGrayInnerWindow;
    private Mat					 mGraySideWindow;
    
    /** 
     * Kernels
     */
    private Mat lapKernel;
    private Mat bwKernel;
    private Mat prewittKernel;
    private Mat highpassKernel;
    

    // Load Callback
    private BaseLoaderCallback  mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                    Log.i(TAG, "OpenCV loaded successfully");
                    mOpenCvCameraView.enableView();
                } break;
                default:
                {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };
    
    // Constructor
    public ImageManipulationsActivity() {
        Log.i(TAG, "Instantiated new " + this.getClass());
    }

    /** Called when the activity is first created. */
    // Declared in base class.
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "called onCreate");
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        setContentView(R.layout.image_manipulations_surface_view);

        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.image_manipulations_activity_surface_view);
        mOpenCvCameraView.setCvCameraViewListener(this);
        
        // toggle checkbox
        addListenerOnChkHistNorm();
    }
    
    // Set Listener for chkHistNorm
    private void addListenerOnChkHistNorm(){
    	chkHistNorm = (CheckBox) findViewById(R.id.checkBox1);
    	chkHistNorm.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View v) {
				if (((CheckBox) v).isChecked()){
					bHistNorm = true;
					Toast.makeText(ImageManipulationsActivity.this, "Histogram Normalization Enabled", Toast.LENGTH_LONG).show();
				}
				else{
					bHistNorm = false;
					Toast.makeText(ImageManipulationsActivity.this, "Histogram Normalization Disabled", Toast.LENGTH_LONG).show();
				}
			}
    	});
    }

    @Override
    public void onPause()
    {
        super.onPause();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    @Override
    public void onResume()
    {
        super.onResume();
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
    }

    // Deconstructor
    public void onDestroy() {
        super.onDestroy();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        Log.i(TAG, "called onCreateOptionsMenu");
        
        mItemBlur		= menu.addSubMenu("Blur");
        mItemBlur.add(0, MODE_GAUSS_11, 0, "Gaussian, 11");
        mItemBlur.add(0, MODE_GAUSS_5, 0, "Gaussian, 5");
        mItemBlur.add(0, MODE_MEDIAN_5, 0, "Median, 5");
        mItemBlur.add(0, MODE_MEDIAN_7, 0, "Median, 7");
        mItemBlur.add(0, MODE_BWLOW, 0, "Butterworth Lowpass");
        
        mItemSharpen	= menu.addSubMenu("Sharpen");
        mItemSharpen.add(0, MODE_LAPS, 0, "Laplacian");
        mItemSharpen.add(0, MODE_HIGHPASS, 0, "Ideal Highpass Filter");
        mItemSharpen.add(0, MODE_SOBEL_VER, 0, "Sobel, Vertical");
        mItemSharpen.add(0, MODE_SOBEL_HOR, 0, "Sobel, Horizontal");
        
        mItemNeg		= menu.add("Negative");
        
        mItemHist		= menu.add("Histogram");
        
        //mItemFFT		= menu.add("Picture FFT");
        return true;
        
    }

    // UI Event Handler
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);
        
        curMode = item.getItemId();
        // Change curMode on selection
        if (item == mItemNeg)	curMode = MODE_NEG;
        if (item == mItemHist)	curMode = MODE_HIST;
        if (item == mItemBlur){
        	curMode = item.getItemId();
        }
        if (item == mItemSharpen){
        	curMode = item.getItemId();
        }
        
        return true;
    }

    // CameraView Initializer
    public void onCameraViewStarted(int width, int height) {
        mGray = new Mat();
        mRgba = new Mat();
        mIntermediateMat = new Mat();
        mSize0 = new Size();

    }

    // Prepare mats
    private void CreateAuxiliaryMats() {
        if (mRgba.empty())
            return;

        mSizeRgba = mRgba.size();

        int rows = (int) mSizeRgba.height;
        int cols = (int) mSizeRgba.width;

        int left = cols / 8;
        int top = rows / 8;

        int width = cols * 3 / 4;
        int height = rows * 3 / 4;

        // Cut the center out
        if (mRgbaInnerWindow == null)
            mRgbaInnerWindow = mRgba.submat(top, top + height, left, left + width);
        mSizeRgbaInner = mRgbaInnerWindow.size();

        if (mGrayInnerWindow == null && !mGray.empty())
            mGrayInnerWindow = mGray.submat(top, top + height, left, left + width);
        
        /**
         * Kernels
         */
        lapKernel = new Mat(3,3,CvType.CV_32FC1);
        lapKernel.put(0, 0, 0);
    	lapKernel.put(2, 0, 0);
    	lapKernel.put(0, 2, 0);
    	lapKernel.put(2, 2, 0);
    	lapKernel.put(1, 1, 5);
    	lapKernel.put(0, 1, -1);
    	lapKernel.put(1, 0, -1);
    	lapKernel.put(2, 1, -1);
    	lapKernel.put(1, 2, -1);
    	
    	/**
    	 *  1 0 -1
    	 *  1 0 -1
    	 *  1 0 -1
    	 */
        prewittKernel = new Mat(3,3,CvType.CV_32FC1);
        prewittKernel.put(0, 0, 1);
    	prewittKernel.put(1, 0, 1);
    	prewittKernel.put(2, 0, 1);
    	prewittKernel.put(0, 1, 0);
    	prewittKernel.put(1, 1, 0);
    	prewittKernel.put(2, 1, 0);
    	prewittKernel.put(0, 2, -1);
    	prewittKernel.put(1, 2, -1);
    	prewittKernel.put(2, 2, -1);
                      
    	bwKernel = new Mat(11,11,CvType.CV_32FC1);
    	double tmp[][] = {
    			{0.002854, 0.000000, 0.002378, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.003329, 0.000000, 0.003092, },
    			{0.000000, 0.002140, 0.000000, 0.000000, 0.012366, 0.060642, 0.004518, 0.004518, 0.000000, 0.003567, 0.000000, },
    			{0.002378, 0.000000, 0.000000, 0.006421, 0.000000, 0.000000, 0.000000, 0.000000, 0.006183, 0.000000, 0.003329, },
    			{0.000000, 0.000000, 0.006421, 0.000000, 0.039715, 0.060642, 0.000000, 0.012366, 0.000000, 0.004518, 0.000000, },
    			{0.000000, 0.012366, 0.000000, 0.039715, 0.000000, 0.025208, 0.000000, 0.000000, 0.000000, 0.004518, 0.000000, },
    			{0.000000, 0.060642, 0.000000, 0.060642, 0.025208, 0.060642, 0.025208, 0.060642, 0.000000, 0.060642, 0.000000, },
    			{0.000000, 0.004518, 0.000000, 0.000000, 0.000000, 0.025208, 0.000000, 0.039715, 0.000000, 0.012366, 0.000000, },
    			{0.000000, 0.004518, 0.000000, 0.012366, 0.000000, 0.060642, 0.039715, 0.000000, 0.006421, 0.000000, 0.000000, },
    			{0.003329, 0.000000, 0.006183, 0.000000, 0.000000, 0.000000, 0.000000, 0.006421, 0.000000, 0.000000, 0.002378, },
    			{0.000000, 0.003567, 0.000000, 0.004518, 0.004518, 0.060642, 0.012366, 0.000000, 0.000000, 0.002140, 0.000000, },
    			{0.003092, 0.000000, 0.003329, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.002378, 0.000000, 0.002854, },
    			};
    	for (int i = 0; i < 11; i ++ )
    		for ( int j = 0; j < 11; j ++ ){
    			bwKernel.put(i, j, tmp[i][j]);
    		}
    	
    	highpassKernel = new Mat(11,11,CvType.CV_32FC1);
    	double tmp2[][] = {
    			{0.000000, 0.000000, 0.004000, 0.004800, 0.007200, 0.025500, 0.000000, 0.006100, 0.000000, 0.000000, 0.001700, },
    			{0.000000, 0.000500, 0.006600, 0.004000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, },
    			{0.004000, 0.006600, 0.008800, 0.023300, 0.001400, 0.025500, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, },
    			{0.004800, 0.004000, 0.023300, 0.025500, 0.006600, 0.000000, 0.016500, 0.000000, 0.000000, 0.000000, 0.006100, },
    			{0.007200, 0.000000, 0.001400, 0.006600, 0.025500, 0.013000, 0.000000, 0.016500, 0.000000, 0.000000, 0.000000, },
    			{0.025500, 0.000000, 0.025500, 0.000000, 0.013000, 0.025500, 0.013000, 0.000000, 0.025500, 0.000000, 0.025500, },
    			{0.000000, 0.000000, 0.000000, 0.016500, 0.000000, 0.013000, 0.025500, 0.006600, 0.001400, 0.000000, 0.007200, },
    			{0.006100, 0.000000, 0.000000, 0.000000, 0.016500, 0.000000, 0.006600, 0.025500, 0.023300, 0.004000, 0.004800, },
    			{0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.025500, 0.001400, 0.023300, 0.008800, 0.006600, 0.004000, },
    			{0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.004000, 0.006600, 0.000500, 0.000000, },
    			{0.001700, 0.000000, 0.000000, 0.006100, 0.000000, 0.025500, 0.007200, 0.004800, 0.004000, 0.000000, 0.000000, },
    	};
    	for ( int i = 0; i < 11; i ++ )
    		for ( int j = 0; j < 11; j ++ ){
    			highpassKernel.put(i, j, tmp2[i][j]);
    		}
    }

    public void onCameraViewStopped() {
        // Explicitly deallocate Mats
        if (mGrayInnerWindow != null)
            mGrayInnerWindow.release();
        if (mRgbaInnerWindow != null)
            mRgbaInnerWindow.release();
        if (mRgba != null)
            mRgba.release();
        if (mGray != null)
            mGray.release();
        if (mIntermediateMat != null)
            mIntermediateMat.release();

        mRgba = null;
        mGray = null;
        mIntermediateMat = null;
        mRgbaInnerWindow = null;
        mGrayInnerWindow = null;
    }
    
    Mat gray = null;
    Mat mat255 = null;

    // Main loop procedure
    // Called on each frame of the camera
    public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
    	mGray = inputFrame.gray();
    	mRgba = inputFrame.rgba();
        
        if ((mRgbaInnerWindow == null) || (mGrayInnerWindow == null) || (mRgba.cols() != mSizeRgba.width) || (mRgba.height() != mSizeRgba.height))
            CreateAuxiliaryMats();
        
    	// histNormalize
    	if ( bHistNorm ){
    		mIntermediateMat = mGray.clone();
    		Imgproc.equalizeHist(mGray, mIntermediateMat);
    		mIntermediateMat.copyTo(mGray);
//    		Imgproc.cvtColor(mIntermediateMat, mRgba, Imgproc.COLOR_GRAY2BGR);
    	}
        
        switch(curMode){
        
        case MODE_GAUSS_5:
        	Imgproc.GaussianBlur(mGrayInnerWindow, mIntermediateMat, new Size(5,5), 5);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA, 4);
        	break;
        	
        case MODE_GAUSS_11:
        	Imgproc.GaussianBlur(mGrayInnerWindow, mIntermediateMat, new Size(7,7), 11);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA, 4);
        	break;
        	
        case MODE_NEG:
        	// construct mat filled with 255
        	if (mat255 == null){
        		mat255 = mGrayInnerWindow.clone();
        		mat255.setTo(new Scalar(255));
        	}
        	Core.subtract(mat255, mGrayInnerWindow, mIntermediateMat);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	
        case MODE_MEDIAN_7:
        	Imgproc.medianBlur(mGrayInnerWindow, mIntermediateMat, 7);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	
        case MODE_MEDIAN_5:
        	Imgproc.medianBlur(mGrayInnerWindow, mIntermediateMat, 5);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	
        case MODE_BWLOW:
        	Imgproc.filter2D(mGrayInnerWindow, mIntermediateMat, mGrayInnerWindow.depth(), bwKernel);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	        	
        case MODE_FFT:
        	Mat padded;        	
        	
        	Core.dft(mGrayInnerWindow, mIntermediateMat);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	
        case MODE_LAPS:
        	Imgproc.filter2D(mGrayInnerWindow, mIntermediateMat, mGrayInnerWindow.depth(), lapKernel);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	
        case MODE_HIGHPASS:
        	Imgproc.filter2D(mGrayInnerWindow, mIntermediateMat, mGrayInnerWindow.depth(), highpassKernel);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	
        case MODE_SOBEL_VER:
        	Imgproc.Sobel(mGrayInnerWindow, mIntermediateMat, 0, 1, 0);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	
        case MODE_SOBEL_HOR:
        	Imgproc.Sobel(mGrayInnerWindow, mIntermediateMat, 0, 0, 1);
        	Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	break;
        	
        case MODE_HIST:
        	Mat mHist = new Mat();
        	int histSize = 20;
        	Imgproc.calcHist(
        			Arrays.asList(mGray), 
        			new MatOfInt(0), 
        			new Mat(), 
        			mHist, 
        			new MatOfInt(histSize), 
        			new MatOfFloat(0f, 256f)
        			);
        	float[] buf = new float[histSize];
        	mHist.get(0,0,buf);
        	Core.normalize(mHist, mHist,mGrayInnerWindow.height() / 2, 0, Core.NORM_INF);
        	// Draw Histogram
        	mHist.get(0, 0, buf);
        	Point p1 = new Point(), p2 = new Point();
        	mIntermediateMat = mRgbaInnerWindow.clone();
			for (int i = 0; i < histSize; i++) {
				p1.x = p2.x = i * mGrayInnerWindow.width() / histSize;
				p1.y = mGrayInnerWindow.height();
				p2.y = mGrayInnerWindow.height() - buf[i];
				Core.line(mIntermediateMat, p1, p2, new Scalar(200, 200, 200,
						100), 3);
			}
			mIntermediateMat.copyTo(mRgbaInnerWindow);
			//Imgproc.cvtColor(mIntermediateMat, mRgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA);
        	
        }
        
        return mRgba;
    }
}
